package cn.accjiyun.gomoku.engine;

import cn.accjiyun.gomoku.model.ChessPoint;
import cn.accjiyun.gomoku.model.PointValue;
import cn.accjiyun.gomoku.model.Role;

import java.util.List;
import java.util.Stack;

/**
 * Created by jiyun on 2016/9/16.
 */
public class GomokuAI {

    private PointValue bestMove;
    private int[][] forecastChessboard;
    private Stack<ChessPoint> chessStack;
    private int boardSize;
    private int maxDepth;
    private int maxWight;
    private int currentDepth;
    private GetNextMoves nextMoves;
    public final static int INFINITY = 9999999;
    private long thinkTime;
    private long totalNode;

    public long getThinkTime() {
        return thinkTime;
    }

    public long getTotalNode() {
        return totalNode;
    }

    public Stack<ChessPoint> getChessStack() {
        return chessStack;
    }

    public int getBoardSize() {
        return boardSize;
    }

    public GomokuAI(Stack<ChessPoint> chessStack, int boardSize, int maxDepth, int maxWight) {
        this.maxDepth = maxDepth;
        this.maxWight = maxWight;
        this.boardSize = boardSize;
        this.chessStack = (Stack<ChessPoint>) chessStack.clone();
        updateForecastBoard();
    }

    public void updateForecastBoard() {
        forecastChessboard = new int[boardSize + 5][boardSize + 5];
        for (ChessPoint chessPoint : chessStack) {
            forecastChessboard[chessPoint.getX()][chessPoint.getY()] = chessPoint.getRole();
        }
    }

    public PointValue getBestMove() {
        long startTime = System.currentTimeMillis();
        bestMove = new PointValue();
        if (chessStack.size() == 0) {
            bestMove.setX(boardSize / 2 + 1);
            bestMove.setY(boardSize / 2 + 1);
            return bestMove;
        }
        if (chessStack.size() == 1) {
            do {
                bestMove.setX(chessStack.peek().getX() + (int) (Math.random() * 100) % 3 - 1);
                bestMove.setY(chessStack.peek().getY() + (int) (Math.random() * 100) % 3 - 1);
            } while (forecastChessboard[bestMove.getX()][bestMove.getY()] != Role.EMPTY);
            return bestMove;
        }
//        for (int depth = 1; depth < 12; depth++) {
//        AlphaBeta(maxDepth, -INFINITY - 1, INFINITY);
        Alpha(maxDepth, INFINITY);
//        }
        thinkTime = System.currentTimeMillis() - startTime;
        return bestMove;
    }

    private int AlphaBeta(int depth, int alpha, int beta) {
        totalNode++;
        if (depth <= 0) {
            Evaluate evaluate = new Evaluate(this);
            int weigh = evaluate.complexionWeigh();
            return weigh - (maxDepth - depth) * 100;
        }
        nextMoves = new GetNextMoves(this, maxWight);
        List<PointValue> nextMovesMoves = nextMoves.getPossibleMoveList();
        if (nextMovesMoves.isEmpty()) return -INFINITY;
        for (PointValue pointValue : nextMovesMoves) {
            makeNextMove(pointValue);
            int value;
            if (CheckWin.CheckWin(chessStack, boardSize) != null) {
                if (depth == maxDepth) {
                    bestMove = pointValue;
                }
                unmakeMove(pointValue);
                return INFINITY;
            }

            if (alpha + 1 < beta) {
                value = -AlphaBeta(depth - 1, -alpha - 1, -alpha);
                if (value > alpha && value < beta) {
                    value = -AlphaBeta(depth - 1, -beta, -value);
                }
            } else {
                value = -AlphaBeta(depth - 1, -beta, -alpha);
            }
            unmakeMove(pointValue);
            if (value >= beta) {
                bestMove = pointValue;
                return value;
            }
            if (value > alpha) {
                alpha = value;
                bestMove = pointValue;
            }
            if (alpha >= beta) break;
        }
        return alpha;
    }

    public int Alpha(int depth, int beta) {
        totalNode++;
        if (depth <= 0) {
            Evaluate evaluate = new Evaluate(this);
            int weigh = evaluate.complexionWeigh();
            return weigh - (maxDepth - depth) * 100;
        }
        nextMoves = new GetNextMoves(this, maxWight);
        List<PointValue> nextMovesMoves = nextMoves.getPossibleMoveList();
        if (nextMovesMoves.isEmpty()) return -INFINITY;
        int alpha = -INFINITY - 1;
        for (PointValue pointValue : nextMovesMoves) {
            makeNextMove(pointValue);
            int value;
            if (CheckWin.CheckWin(chessStack, boardSize) != null) {
                if (depth == maxDepth) {
                    bestMove = pointValue;
                }
                unmakeMove(pointValue);
                return INFINITY;
            }
            value = Beta(depth - 1, alpha);
            unmakeMove(pointValue);
            if (value > beta) {
                return value;
            }
            if (value > alpha) {
                alpha = value;
                if (depth == maxDepth) {
                    bestMove = pointValue;
                }
            }
        }
        return alpha;
    }

    public int Beta(int depth, int alpha) {
        totalNode++;
        if (depth == 0) {
            Evaluate evaluate = new Evaluate(this);
            int weigh = evaluate.complexionWeigh();
            return weigh - (maxDepth - depth) * 100;
        }
        nextMoves = new GetNextMoves(this, maxWight);
        List<PointValue> nextMovesMoves = nextMoves.getPossibleMoveList();
        if (nextMovesMoves.isEmpty()) return INFINITY;
        int beta = INFINITY;
        for (PointValue pointValue : nextMovesMoves) {
            makeNextMove(pointValue);
            int value;
            if (CheckWin.CheckWin(chessStack, boardSize) != null) {
                unmakeMove(pointValue);
                return -INFINITY;
            }
            value = Alpha(depth - 1, beta);
            unmakeMove(pointValue);
            if (value < alpha) {
                return value;
            }
            if (value < beta) {
                beta = value;
            }
        }
        return beta;
    }

    public void makeNextMove(PointValue pointValue) {
        chessStack.push(pointValue);
        forecastChessboard[pointValue.getX()][pointValue.getY()] = pointValue.getRole();
    }

    public void unmakeMove(PointValue pointValue) {
        if (chessStack.isEmpty()) {
            return;
        }
        chessStack.pop();
        forecastChessboard[pointValue.getX()][pointValue.getY()] = Role.EMPTY;
    }
}
